--- title: "02-Spring Boot 跨域(CORS)问题" created: 2025-12-02 tags: - 项目 aliases: - Spring Boot 跨域(CORS)问题 --- # Spring Boot 跨域(CORS)问题 ## **一、什么是跨域问题?** ### **1.1 同源策略** 浏览器出于安全考虑,实施了**同源策略**(Same-Origin Policy),限制网页向不同源的服务器发送请求。 **同源的定义**:协议 + 域名 + 端口 完全相同 ```text ✅ 同源示例: http://localhost:8080/api/user http://localhost:8080/api/order ❌ 不同源示例: http://localhost:5173 → http://localhost:8080 (端口不同) http://example.com → https://example.com (协议不同) http://www.a.com → http://www.b.com (域名不同) ``` ### **1.2 跨域场景** ```text ┌─────────────────────┐ ┌─────────────────────┐ │ 前端 Vue 应用 │ HTTP │ 后端 Spring Boot │ │ localhost:5173 │ ──────▶ │ localhost:8080 │ └─────────────────────┘ 跨域! └─────────────────────┘ ``` 当前端(如 Vue)和后端(如 Spring Boot)部署在不同端口/域名时,浏览器会阻止请求。 --- ## **二、CORS 机制解析** ### **2.1 什么是 CORS?** **CORS**(Cross-Origin Resource Sharing,跨域资源共享)是 W3C 标准,通过服务器设置 HTTP 响应头,告诉浏览器允许哪些跨域请求。 ### **2.2 CORS 响应头说明** | **响应头** | **说明** | **示例值** | | --- | --- | --- | | `Access-Control-Allow-Origin` | 允许的来源域名 | `http://localhost:5173` 或 `*` | | `Access-Control-Allow-Methods` | 允许的 HTTP 方法 | `GET, POST, PUT, DELETE` | | `Access-Control-Allow-Headers` | 允许的请求头 | `Content-Type, Authorization` | | `Access-Control-Allow-Credentials` | 是否允许携带 Cookie | `true` / `false` | | `Access-Control-Max-Age` | 预检请求缓存时间(秒) | `3600` | ### **2.3 简单请求 vs 预检请求** ```text 简单请求(直接发送): ┌────────┐ GET/POST ┌────────┐ │ 浏览器 │ ─────────▶ │ 服务器 │ └────────┘ └────────┘ 预检请求(先 OPTIONS 探测): ┌────────┐ OPTIONS ┌────────┐ │ 浏览器 │ ─────────▶ │ 服务器 │ ← 预检请求 │ │ ◀───────── │ │ ← 返回 CORS 头 │ │ PUT/DELETE│ │ │ │ ─────────▶ │ │ ← 实际请求 └────────┘ └────────┘ ``` **触发预检请求的条件**: - 使用 `PUT`、`DELETE`、`PATCH` 方法 - 自定义请求头(如 `Authorization`) - `Content-Type` 不是 `application/x-www-form-urlencoded`、`multipart/form-data`、`text/plain` --- ## **三、解决方案演进** ### **方案一:WebMvcConfigurer(基础硬编码)** 最简单的方式,直接在代码中写死允许的域名。 ```java package com.example.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 所有路径 .allowedOrigins( // 允许的域名(硬编码) "http://localhost:5173", "http://127.0.0.1:5173" ) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) // 允许携带 Cookie .maxAge(3600); // 预检缓存 1 小时 } } ``` **优点**:简单直接 **缺点**:修改域名需要改代码并重新部署 --- ### **方案二:Filter 过滤器(精细控制)** 通过过滤器手动设置响应头,适合需要更精细控制的场景。 ```java package com.example.config; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.io.IOException; @Component @Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级,确保最先执行 public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 动态获取请求来源 String origin = request.getHeader("Origin"); if (origin != null) { response.setHeader("Access-Control-Allow-Origin", origin); } // 设置其他 CORS 头 response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Max-Age", "3600"); // 动态获取请求头 String headers = request.getHeader("Access-Control-Request-Headers"); if (headers != null) { response.setHeader("Access-Control-Allow-Headers", headers); } // 预检请求直接返回 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); return; } chain.doFilter(request, response); } } ``` **优点**:可以动态处理、精细控制 **缺点**:允许所有来源可能有安全风险 --- ### **方案三:从配置文件读取(推荐)** 将允许的域名配置到 `application.yml` 中,无需改代码即可调整。 #### **3.1 配置文件** ```yaml # application.yml cors: allowed-origins: ${APP_CORS_ORIGINS:http://localhost:5173,http://localhost:3000} ``` #### **3.2 配置属性类** ```java package com.example.config.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.List; @Data @Component @ConfigurationProperties(prefix = "cors") public class CorsProperties { /** * 允许的跨域来源列表 */ private List allowedOrigins; } ``` #### **3.3 CORS 配置类** ```java package com.example.config; import com.example.config.properties.CorsProperties; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @RequiredArgsConstructor public class CorsConfig implements WebMvcConfigurer { private final CorsProperties corsProperties; @Override public void addCorsMappings(CorsRegistry registry) { // 从配置文件读取允许的域名 String[] origins = corsProperties.getAllowedOrigins() .toArray(new String[0]); registry.addMapping("/**") .allowedOrigins(origins) // 动态配置 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } } ``` #### **3.4 环境变量文件** ```properties # .env.dev - 开发环境 APP_CORS_ORIGINS=http://localhost:5173,http://localhost:3000,http://127.0.0.1:5173 # .env.prod - 生产环境 APP_CORS_ORIGINS=https://www.example.com,https://admin.example.com ``` **优点**: - 不同环境配置不同域名 - 无需改代码,改配置即可 - 敏感信息不提交 Git --- ## **四、其他跨域解决方案** ### **4.1 前端代理(开发环境)** 在前端开发服务器配置代理,将请求转发到后端。 ```javascript // vite.config.js (Vue 3 + Vite) export default { server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } } ``` ```javascript // vue.config.js (Vue 2 + Webpack) module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } } ``` **注意**:仅适用于开发环境,生产环境需要后端配置 CORS。 ### **4.2 Nginx 反向代理(生产环境)** ```nginx server { listen 80; server_name www.example.com; # 前端静态资源 location / { root /var/www/html; try_files $uri $uri/ /index.html; } # 后端 API 代理 location /api { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` **优点**:前后端同域,无需配置 CORS ### **4.3 JSONP(已过时)** 通过 `